/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include <stdio.h>
#include "cc_compat.h"
#include "ps2.h"
#include "keyboard.h"
#include "queue.h"
#include "sdi.h"
#include "timer.h"


extern unsigned char code key_EnE3867_matrix_map[];
extern unsigned char code key_set1_extended_key_map[];

#define KEY_TYPE_EnE3867		1

#define KEY_UP_QUEUE_DEPTH			8
#define KEY_PRIORITY_QUEUE_DEPTH	4
#define KEY_COMMAND_QUEUE_DEPTH		4

void key_set1_send_extended(unsigned char kv,unsigned char got_break);

unsigned char key_kmap(unsigned char MV);

static unsigned char xdata key_up_head;
static unsigned char xdata key_up_tail;
unsigned char xdata key_up_queue[KEY_UP_QUEUE_DEPTH];

static unsigned char xdata key_pri_head;
static unsigned char xdata key_pri_tail;
unsigned char xdata key_pri_queue[KEY_PRIORITY_QUEUE_DEPTH];

static unsigned char xdata key_cmd_head;
static unsigned char xdata key_cmd_tail;
unsigned char xdata key_cmd_queue[KEY_COMMAND_QUEUE_DEPTH];

static unsigned char xdata key_state;
static unsigned char xdata key_state_prev;

static unsigned char xdata key_got_break;
static unsigned char xdata key_type;

static char key_debug_on;

#define KEY_SHOW_STATES 	0
#define KEY_SHOW_ADD		0

#define KEY_DISABLED	0
#define KEY_IDLE		1
#define KEY_KB_DATA		2

void key_reset(void)
{
	key_state		= KEY_DISABLED;
	key_state_prev	= 1;
	key_got_break	= 0;
	key_type 		= KEY_TYPE_EnE3867;
	key_up_head  = key_up_tail  = 0;
	key_pri_head = key_pri_tail = 0;
	key_cmd_head = key_cmd_tail = 0;

}

void key_set_debug(char on)
{
	key_debug_on = on;
}

void key_send_data(unsigned char kv)
{
	if (key_debug_on)
	{
		print_tstamp();
		printf("ku>: %02x\n",(uint)kv);
	}
	sdi_up_queue_add(SDI_CHANNEL_KEYBOARD,kv);
}

int key_up_queue_add(unsigned char val)
{
	if (QUEUE_SIZE(key_up_head,key_up_tail) >= (KEY_UP_QUEUE_DEPTH) )
	{
		return QUEUE_FULL;
	}
	key_up_queue[key_up_head & (KEY_UP_QUEUE_DEPTH-1)] = val;
	key_up_head++;
	return 0;
}

int key_up_queue_get(void)
{
	int retval;

	if (QUEUE_SIZE(key_up_head,key_up_tail) == 0 )
	{
		return QUEUE_EMPTY;
	}
	retval = (int)key_up_queue[key_up_tail & (KEY_UP_QUEUE_DEPTH-1)];
	key_up_tail++;
	return retval;
}

unsigned char key_up_queue_size(void)
{
	unsigned char size;

	size = QUEUE_SIZE(key_up_head,key_up_tail);

#if 0
	{
		static unsigned kb_up_oldsize;

		if (size != kb_up_oldsize)
		{
			kb_up_oldsize = size;
			print_tstamp();
			printf("\n<H:%d,T:%d>",(unsigned int)key_Up_Head,(unsigned int)KB_Up_Tail);
		}
	}
#endif
	return size;
}

#ifdef UNUSED
int key_cmd_queue_add(unsigned char val)
{
	if (QUEUE_SIZE(key_cmd_head,key_cmd_tail) >= (KEY_COMMAND_QUEUE_DEPTH) )
	{
		return QUEUE_FULL;
	}
	key_cmd_queue[key_cmd_head & (KEY_COMMAND_QUEUE_DEPTH-1)] = val;
	key_cmd_head++;
	return 0;
}

int key_cmd_queue_get(void)
{
	int retval;

	if (QUEUE_SIZE(key_cmd_head,key_cmd_tail) == 0 )
	{
		return QUEUE_EMPTY;
	}
	retval = (int)key_cmd_queue[key_cmd_tail & (KEY_COMMAND_QUEUE_DEPTH-1)];
	key_cmd_tail++;
	return retval;
}

unsigned char key_cmd_queue_size(void)
{
	unsigned char size;

	size = QUEUE_SIZE(key_cmd_head,key_cmd_tail);
	return size;
}
#endif

void key_start(void)
{
	key_state = KEY_IDLE;
}

void key_process(void)
{
	unsigned char qsize;
	unsigned char tries;
	unsigned char mv;
	unsigned char kv;

	qsize = key_up_queue_size();

	for (tries=0; tries<qsize; tries++)
	{
		if ( sdi_up_queue_ready() )
		{
			key_send_data( key_up_queue_get() );
		}
	}

#if KEY_SHOW_STATES
	if (key_state_prev != key_state)
	{
		key_state_prev = key_state;
		print_tstamp();
		printf("KEY=%d\n",
			   (unsigned int)key_state
			  );
	}
#endif

	switch (key_state)
	{
		case KEY_DISABLED:
			break;

		case KEY_IDLE:
			if (ps2_kb_queue_size() > 0 )
			{
				key_state = KEY_KB_DATA;
			}
			break;

		case KEY_KB_DATA:

			// If there's not at least 2 bytes availabe in the
			// up queue then don't do anthing.
			if (key_up_queue_size() > (KEY_UP_QUEUE_DEPTH-2) )
			{
				key_state = KEY_IDLE;
				break;
			}

			if (key_got_break)
			{
				key_got_break = 0;
				// Last value was a break code
				// Need to set the 0x80 bit of the keyvalue
				mv = ps2_kb_queue_get();
				kv = key_kmap(mv);

				// If its non-extended key just send it
				if (kv < 0x80)
				{
					key_up_queue_add(kv|0x80);
				}
				else
				{
					// If its extended then
					// this is a add multiple.
					key_set1_send_extended(kv,1);
				}
			}
			else
			{
				mv = ps2_kb_queue_get();

				if (0xf0 == mv)
				{
					key_got_break = 1;
				}
				else
				{
					kv = key_kmap(mv);
					if (kv < 0x80)
					{
						key_up_queue_add(kv);
					}
					else
					{
						// If its extended then
						// this is a add multiple.
						key_set1_send_extended(kv,0);
					}
				}
			}
			key_state = KEY_IDLE;

			break;
	}
}

unsigned char key_kmap(unsigned char MV)
{
	switch (key_type)
	{
		case KEY_TYPE_EnE3867:
			return key_EnE3867_matrix_map[MV];
			break;
		default:
			printf("Unknown keyboard type");
			return 0;
	}
}

void key_set1_send_extended(unsigned char kv,unsigned char got_break)
{
	unsigned char ev_index;
	unsigned char ev;

	// Maxtrix map codes > 0x7f are an index into
	// the extended table.  Extended codes have
	// a 0xe0 prepended to them
	ev_index = (kv-0x80);

	ev = key_set1_extended_key_map[ev_index];

	key_up_queue_add(0xe0);

	if (got_break)
	{
		key_up_queue_add(ev|0x80);
	}
	else
	{
		key_up_queue_add(ev);
	}
}

// This table maps the EnE3867 scan address (maxtrix value)
// directly to a set 1 code or if the high bit is set
// into the extended map
// which is used for multi-code keys.

// TODO: Get the Fn .5 keys from an alps keyboard

unsigned char code key_EnE3867_matrix_map[] =
{
//  KSI   0    1    2    3    4    5    6    7
	0x00,0x00,0x1d,0x00,0x00,0x00,0x00,0x87,	// KSO 0
	0x29,0x01,0x0f,0x29,0x1e,0x2c,0x02,0x10,	// KSO 1
	0x3b,0x3e,0x3d,0x3c,0x20,0x2e,0x04,0x12,	// KSO 2
	0x00,0x88,0x00,0x00,0x00,0x89,0x00,0x00,	// KSO 3
	0x30,0x22,0x14,0x06,0x21,0x2f,0x05,0x13,	// KSO 4
	0x42,0x41,0x40,0x3f,0x1f,0x2d,0x03,0x11,	// KSO 5
	0x73,0x3f,0x1b,0x2b,0x25,0x33,0x09,0x17,	// KSO 6
	0x8a,0x00,0x00,0x59,0x00,0x00,0x00,0x00,	// KSO 7
	0x31,0x23,0x15,0x07,0x24,0x32,0x08,0x16,	// KSO 8
	0x00,0x00,0x00,0x00,0x00,0x2a,0x00,0x36,	// KSO 9
	0x0d,0x28,0x1a,0x0c,0x27,0x35,0x0b,0x19,	// KSO a
	0x58,0x57,0x44,0x43,0x26,0x34,0x0a,0x18,	// KSO b
	0x00,0x74,0x39,0x8b,0x00,0x00,0x00,0x00,	// KSO c
	0x86,0x00,0x00,0x00,0x00,0x00,0x38,0x00,	// KSO d
	0x80,0x0e,0x00,0x2b,0x1c,0x39,0x84,0x85,	// KSO e
	0x81,0x40,0x1b,0x0d,0x00,0x28,0x82,0x83,	// KSO f
};


// Each of these keycodes expand to a 2 byte sequence.
// 0xe0 and then the respective value.
unsigned char code key_set1_extended_key_map[] =
{
	0x52,	// 0x80 Insert
	0x53,	// 0x81 Delete
	0x4d,	// 0x82 Right Arrow
	0x4b,	// 0x83 Left Arrow
	0x50,	// 0x84 Down Arrow
	0x48,	// 0x85 Up Arrow
	0x38,	// 0x86 Right Alt (AltGR)
	0x79,	// 0x87 Magnifiger
	0x5b,	// 0x88 Left Hand
	0x5c,	// 0x89 Right Hand
	0x6e,	// 0x8a Blackboard
	0x5d,	// 0x8b Frame
};

#ifdef UNUSED
void key_set_type(unsigned char type)
{
	key_type = type;
}
#endif
